#include "../pch.h"
#include "REFXMLName.h"

namespace BFX
{

REFXMLAttributeMap::REFXMLAttributeMap(REFXMLNode* pParent) :
	REFXMLNodeMap(pParent)
{
}

REFXMLAttributeMap::~REFXMLAttributeMap()
{
}

// Gets the attribute with the specified index.
REFXMLAttribute* REFXMLAttributeMap::GetAttribute(int iIndex) const
{
	return (REFXMLAttribute*)GetNode(iIndex);
}
// Gets the attribute with the specified name.
REFXMLAttribute* REFXMLAttributeMap::GetAttribute(const UniString& strName) const
{
	return (REFXMLAttribute*)GetNamedItem(strName);
}
// Gets the attribute with the specified LocalName and NamespaceUri.
REFXMLAttribute* REFXMLAttributeMap::GetAttribute(const UniString& strLocalName, const UniString& strNamespaceURI) const
{
	return (REFXMLAttribute*)GetNamedItem(strLocalName, strNamespaceURI);
}

REFXMLNode* REFXMLAttributeMap::SetNamedItem(REFXMLNode* pNode)
{
	BFX_REQUIRE (pNode != NULL && pNode->GetNodeType() == XMLNodeType_Attribute);

	int iOffset = FindNodeOffset(pNode->GetLocalName(), pNode->GetNamespaceURI());
	if (iOffset == -1)
	{
		return AppendAttribute((REFXMLAttribute*)pNode);
	}
	else
	{
		REFXMLNode* pOldNode = REFXMLNodeMap::RemoveNodeAt(iOffset);
		pOldNode->Autorelease();
		InsertNodeAt(iOffset, pNode);

		return pOldNode;
	}
}

// Inserts the specified node as the first node in the collection.
REFXMLAttribute* REFXMLAttributeMap::Prepend(REFXMLAttribute* pNode)
{
	BFX_REQUIRE (pNode != NULL);
	BFX_REQUIRE (pNode->GetOwnerDocument() == NULL || pNode->GetOwnerDocument() == m_pParent->GetOwnerDocument());

	if (pNode->GetOwnerElement() != NULL)
		Detach(pNode);

	RemoveDuplicateAttribute(pNode);

	InsertNodeAt(0, pNode);
	return pNode;
}

// Inserts the specified node as the last node in the collection.
REFXMLAttribute* REFXMLAttributeMap::Append(REFXMLAttribute* pNode)
{
	BFX_REQUIRE (pNode != NULL);
	BFX_REQUIRE (pNode->GetOwnerDocument() == NULL || pNode->GetOwnerDocument() == m_pParent->GetOwnerDocument());

	if (pNode->GetOwnerElement() != NULL)
		Detach(pNode);

	AddNode(pNode);

	return pNode;
}

// Inserts the specified attribute immediately before the specified reference attribute.
REFXMLAttribute* REFXMLAttributeMap::InsertBefore(REFXMLAttribute* pNewNode, REFXMLAttribute* pRefNode)
{
	if ( pNewNode == pRefNode )
		return pNewNode;

	if (pRefNode == NULL)
		return Append(pNewNode);

	BFX_REQUIRE (pRefNode->GetOwnerElement() == m_pParent);
	BFX_REQUIRE (pNewNode->GetOwnerDocument() == NULL || pNewNode->GetOwnerDocument() == m_pParent->GetOwnerDocument());

	if (pNewNode->GetOwnerElement() != NULL)
		Detach(pNewNode);

	int iOffset = FindNodeOffset(pRefNode->GetLocalName(), pRefNode->GetNamespaceURI());
	BFX_ASSERT(iOffset != -1);	// the if statement above guarantees that the ref node is in the collection

	int iDupOffset = RemoveDuplicateAttribute(pNewNode);
	if (iDupOffset >= 0 && iDupOffset < iOffset)
		iOffset--;
	InsertNodeAt(iOffset, pNewNode);

	return pNewNode;
}

// Inserts the specified attribute immediately after the specified reference attribute.
REFXMLAttribute* REFXMLAttributeMap::InsertAfter(REFXMLAttribute* pNewNode, REFXMLAttribute* pRefNode)
{
	if (pNewNode == pRefNode)
		return pNewNode;

	if (pRefNode == NULL)
		return Prepend(pNewNode);

	BFX_REQUIRE (pRefNode->GetOwnerElement() != m_pParent);
	BFX_REQUIRE (pNewNode->GetOwnerDocument() == NULL || pNewNode->GetOwnerDocument() == m_pParent->GetOwnerDocument());

	if (pNewNode->GetOwnerElement() != NULL)
		Detach(pNewNode);

	int iOffset = FindNodeOffset(pRefNode->GetLocalName(), pRefNode->GetNamespaceURI());
	BFX_ASSERT(iOffset != -1);	// the if statement above guarantees that the ref node is in the collection

	int iDupOffset = RemoveDuplicateAttribute(pNewNode);
	if (iDupOffset >= 0 && iDupOffset < iOffset)
		iOffset--;
	InsertNodeAt(iOffset + 1, pNewNode);

	return pNewNode;
}

// Removes the specified attribute node from the map.
REFXMLAttribute* REFXMLAttributeMap::Remove(REFXMLAttribute* pNode)
{
	for (int iOffset = 0; iOffset < GetCount(); iOffset ++)
	{
		if (GetNode(iOffset) == pNode)
		{
			pNode->Autorelease();	// ref-count protection
			RemoveNodeAt(iOffset);
			return pNode;
		}
	}
	return NULL;
}

// Removes the attribute node with the specified index from the map.
REFXMLAttribute* REFXMLAttributeMap::RemoveAt(int i)
{
	if (i < 0 || i >= GetCount())
		return NULL;

	return (REFXMLAttribute*)RemoveNodeAt(i);
}

void REFXMLAttributeMap::RemoveAll()
{
	int n = GetCount();
	while (n-- > 0)
	{
		RemoveAt(n);
	}
}

void REFXMLAttributeMap::Detach(REFXMLAttribute* pAttr)
{
	BFX_ASSERT(pAttr != NULL);
	pAttr->GetOwnerElement()->GetAttributes()->Remove(pAttr);
}

// the function checks if there is already node with the same name existing in the collection
// if so, remove it because the new one will be inserted to replace this one (could be in different position though ) 
// by the calling function later
int REFXMLAttributeMap::RemoveDuplicateAttribute(REFXMLAttribute* pAttr)
{
	int i = FindNodeOffset(pAttr->GetLocalName(), pAttr->GetNamespaceURI());
	if (i != -1)
	{
		REFXMLAttribute* at = (REFXMLAttribute*) GetNode(i);
		REFXMLNodeMap::RemoveNodeAt(i);

		// Remove parent from element id attribute map;
		RemoveParentFromElementIdAttrMap(at);
	}
	return i;
}

// WARNING: 
//  For performance reasons, this function does not check
//  for xml attributes within the collection with the same full name.
//  This means that any caller of this function must be sure that
//  a duplicate attribute does not exist.
REFXMLAttribute* REFXMLAttributeMap::AppendAttribute(REFXMLAttribute* pNode)
{
	// a duplicate node better not exist
	BFX_ASSERT(-1 == FindNodeOffset(pNode->GetName(), pNode->GetNamespaceURI()));

	REFXMLNode* pRetNode = REFXMLNodeMap::AddNode(pNode);
	BFX_ASSERT(pRetNode->GetNodeType() == XMLNodeType_Attribute);

	// Insert parent into element id attribute map.
	InsertParentIntoElementIdAttrMap((REFXMLAttribute*)pNode);

	return (REFXMLAttribute*)pRetNode;
}

REFXMLNode* REFXMLAttributeMap::AddNode(REFXMLNode* pNode)
{
	//should be sure by now that the node doesn't have the same name with an existing node in the collection
	RemoveDuplicateAttribute((REFXMLAttribute*)pNode);

	REFXMLNode* pRetNode = REFXMLNodeMap::AddNode(pNode);
	BFX_ASSERT (pRetNode->GetNodeType() == XMLNodeType_Attribute);

	// Insert parent into element id attribute map.
	InsertParentIntoElementIdAttrMap((REFXMLAttribute*)pNode);

	return pRetNode;
}

REFXMLNode* REFXMLAttributeMap::InsertNodeAt(int i, REFXMLNode* pNode)
{
	REFXMLNode* pRetNode = REFXMLNodeMap::InsertNodeAt(i, pNode);
	BFX_ASSERT (pRetNode->GetNodeType() == XMLNodeType_Attribute);

	// Insert parent into element id attribute map.
	InsertParentIntoElementIdAttrMap((REFXMLAttribute*)pNode);

	return pRetNode;
}

REFXMLNode* REFXMLAttributeMap::RemoveNodeAt(int i)
{
	// remove the node without checking replacement
	REFXMLNode* pRetNode = REFXMLNodeMap::RemoveNodeAt(i);
	BFX_ASSERT(pRetNode->GetNodeType() == XMLNodeType_Attribute);

	// Remove parent from element id attribute map.
	RemoveParentFromElementIdAttrMap((REFXMLAttribute*)pRetNode);
	// after remove the attribute, we need to check if a default attribute node should be created and inserted into the tree
	REFXMLAttribute* pDefAttr = m_pParent->GetOwnerDocument()->GetDefaultAttribute((REFXMLElement*)m_pParent, pRetNode->GetPrefix(), pRetNode->GetLocalName(), pRetNode->GetNamespaceURI());
	if (pDefAttr != NULL)
		InsertNodeAt(i, pDefAttr);
	return pRetNode;
}

// insert the parent element node into the map
void REFXMLAttributeMap::InsertParentIntoElementIdAttrMap(REFXMLAttribute* pAttr)
{
	REFXMLElement* pParentElem = m_pParent->GetNodeType() == XMLNodeType_Element ? (REFXMLElement*)m_pParent : NULL;
	if (pParentElem != NULL)
	{
		if (pParentElem->GetOwnerDocument() == NULL)
			return;
		REFXMLName* pAttrName = pParentElem->GetOwnerDocument()->GetElementIdInfo(pParentElem->GetXmlName());
		if (pAttrName != NULL && pAttrName->GetPrefix() == pAttr->GetXmlName()->GetPrefix() && pAttrName->GetLocalName() == pAttr->GetXmlName()->GetLocalName())
		{
			pParentElem->GetOwnerDocument()->AddElementWithId(pAttr->GetValue(), pParentElem);
		}
	}
}

//remove the parent element node from the map when the ID attribute is removed
void REFXMLAttributeMap::RemoveParentFromElementIdAttrMap(REFXMLAttribute* pAttr)
{
	REFXMLElement* pParentElem = m_pParent->GetNodeType() == XMLNodeType_Element ? (REFXMLElement*)m_pParent : NULL;
	if (pParentElem != NULL)
	{
		if (pParentElem->GetOwnerDocument() == NULL)
			return;
		REFXMLName* pAttrName = pParentElem->GetOwnerDocument()->GetElementIdInfo(pParentElem->GetXmlName());
		if (pAttrName != NULL && pAttrName->GetPrefix() == pAttr->GetXmlName()->GetPrefix() && pAttrName->GetLocalName() == pAttr->GetXmlName()->GetLocalName())
		{
			pParentElem->GetOwnerDocument()->RemoveElementWithId(pAttr->GetValue(), pParentElem);	// remove the element from the hashtable
		}
	}
}

bool REFXMLAttributeMap::PrepareParentInElementIdAttrMap(const UniString& strAttrPrefix, const UniString& strAttrLocalName)
{
	REFXMLElement* pParentElem = m_pParent->GetNodeType() == XMLNodeType_Element ? (REFXMLElement*)m_pParent : NULL;
	BFX_ASSERT(pParentElem != NULL);
	REFXMLDocument* pDoc = m_pParent->GetOwnerDocument();
	BFX_ASSERT(pDoc != NULL);
	// The returned attrname if not null is the name with namespaceURI being set to string.Empty
	// Because DTD doesn't support namespaceURI so all comparisons are based on no namespaceURI (string.Empty);
	REFXMLName* pAttrname = pDoc->GetElementIdInfo(pParentElem->GetXmlName());
	if (pAttrname != NULL && pAttrname->GetPrefix() == strAttrPrefix && pAttrname->GetLocalName() == strAttrLocalName)
	{
		return true;
	}
	return false;
}

void REFXMLAttributeMap::ResetParentInElementIdAttrMap(const UniString& strOldVal, const UniString& strNewVal)
{
	REFXMLElement* pParentElem = m_pParent->GetNodeType() == XMLNodeType_Element ? (REFXMLElement*)m_pParent : NULL;
	BFX_ASSERT(pParentElem != NULL);
	REFXMLDocument* pDoc = pParentElem->GetOwnerDocument();
	BFX_ASSERT(pDoc != NULL);
	pDoc->RemoveElementWithId(strOldVal, pParentElem);	// add the element into the hashtable
	pDoc->AddElementWithId(strNewVal, pParentElem);
}

}	//	namespace BFX
